﻿// <copyright>
// Copyright (c) 2008 - 2009, Microsoft, All Rights Reserved
// </copyright>
//
// Licensed under the Microsoft Public License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.opensource.org/licenses/ms-pl.html

namespace LiveLabs.Logging
{
    using System;

    /// <summary>
    /// Used by applications to write messages and/or exceptions to a <see cref="LiveLabs.Logging.Log"/>.
    /// </summary>
    /// <remarks>
    /// <para>In the most simple form, a Logger is created as a static field of a class.</para>
    /// <example>
    /// public class MyClass
    /// {
    ///     private static readonly Logger logger = new Logger(typeof(MyClass));
    /// }
    /// </example>
    /// <para>Then it is used to write messages to a <see cref="LiveLabs.Logging.Log"/></para>
    /// <example>
    /// logger.Inform("Processing customer order");
    /// try
    /// {
    ///     // some operation that goes horribly wrong
    /// }
    /// catch(Exception ex)
    /// {
    ///     logger.Fatal("Visa Processing informed us that no more processing can be done");
    ///     logger.Fatal(ex);
    /// }
    /// </example>
    /// <para>
    /// <see cref="Logger.Level"/>, message formatters, and <see cref="LiveLabs.Logging.Log"/> are
    /// retrieved from an instance of <see cref="Settings"/>.  This instance can be
    /// passed in via the constructor.  If not, the default for the current AppDomain 
    /// will be used.
    /// </para>
    /// </remarks>
    public class Logger
    {
        // the name of the logger
        private LoggerName name;

        // the Settings instance that our settings are pulled from
        private Settings settings;

        // where the messages will be written
        private Log destinationLog;

        // the low watermark this logger should not log below
        private LogLevel level;

        // the delegate that formats our messages
        private MessageFormatter messageFormatter;

        // the delegate that formats our exceptions
        private ExceptionFormatter exceptionFormatter;

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="name">The name of the logger</param>
        /// <exception cref="ArgumentNullException">
        /// If <paramref name="name"/> is empty or null.
        /// </exception>
        public Logger(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            this.name = new LoggerName(name);
            this.settings = Settings.Default;
            this.destinationLog = this.settings.FindLogFor(this.name);

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="type">The <see cref="System.Type"/> the name of the logger is derived.</param>
        /// <exception cref="ArgumentNullException">
        /// If <paramref name="type"/> is null.
        /// </exception>
        public Logger(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            this.name = new LoggerName(type);
            this.settings = Settings.Default;
            this.destinationLog = this.settings.FindLogFor(this.name);

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="name">The name of the logger</param>
        /// <param name="log">The <see cref="LiveLabs.Logging.Log"/> to write to.</param>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="name"/> is empty or null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="log"/> is null</para>
        /// </exception>
        public Logger(string name, Log log)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (log == null)
            {
                throw new ArgumentNullException("log");
            }

            this.name = new LoggerName(name);
            this.settings = Settings.Default;
            this.destinationLog = log;

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="type">The <see cref="System.Type"/> the name of the logger is derived.</param>
        /// <param name="log">The <see cref="LiveLabs.Logging.Log"/> to write to.</param>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="type"/> is null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="log"/> is null</para>
        /// </exception>
        public Logger(Type type, Log log)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            if (log == null)
            {
                throw new ArgumentNullException("log");
            }

            this.name = new LoggerName(type);
            this.settings = Settings.Default;
            this.destinationLog = log;

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="name">The name of the logger</param>
        /// <param name="settings">The <see cref="Settings"/> to initialize the logger from</param>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="name"/> is empty or null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="settings"/> is null</para>
        /// </exception>
        public Logger(string name, Settings settings)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            this.name = new LoggerName(name);
            this.settings = settings;
            this.destinationLog = this.settings.FindLogFor(this.name);

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="type">The <see cref="System.Type"/> the name of the logger is derived.</param>
        /// <param name="settings">The <see cref="Settings"/> to initialize the logger from</param>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="type"/> is null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="settings"/> is null</para>
        /// </exception>
        public Logger(Type type, Settings settings)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            this.name = new LoggerName(type);
            this.settings = settings;
            this.destinationLog = this.settings.FindLogFor(this.name);

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="name">The name of the logger</param>
        /// <param name="log">The <see cref="LiveLabs.Logging.Log"/> to write to.</param>
        /// <param name="settings">The <see cref="Settings"/> to initialize the logger from</param>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="name"/> is empty or null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="log"/> is null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="settings"/> is null</para>
        /// </exception>
        public Logger(string name, Log log, Settings settings)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (log == null)
            {
                throw new ArgumentNullException("log");
            }

            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            this.name = new LoggerName(name);
            this.settings = settings;
            this.destinationLog = log;

            this.CommonInit();
        }

        /// <summary>
        /// Initializes a new instance of the Logger class.
        /// </summary>
        /// <param name="type">The <see cref="System.Type"/> the name of the logger is derived.</param>
        /// <param name="log">The <see cref="LiveLabs.Logging.Log"/> to write to.</param>
        /// <param name="settings">The <see cref="Settings"/> to initialize the logger from</param>
        /// <exception cref="ArgumentNullException">
        /// <para>If <paramref name="type"/> is null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="log"/> is null</para>
        /// <para>-*-OR-*-</para>
        /// <para>If <paramref name="settings"/> is null</para>
        /// </exception>
        public Logger(Type type, Log log, Settings settings)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            if (log == null)
            {
                throw new ArgumentNullException("log");
            }

            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            this.name = new LoggerName(type);
            this.settings = settings;
            this.destinationLog = log;

            this.CommonInit();
        }

        /// <summary>
        /// Gets or sets the <see cref="LiveLabs.Logging.Log"/> this Logger will write to
        /// </summary>
        /// <remarks>
        /// When setting the <see cref="LiveLabs.Logging.Log"/> if you use <code>null</code>, 
        /// the <see cref="LiveLabs.Logging.Log"/> will revert to what is contained in the <see cref="Settings"/>
        /// this Logger was created from.
        /// </remarks>
        public Log DestinationLog
        {
            get
            {
                return this.destinationLog;
            }

            set
            {
                if (value == null)
                {
                    value = this.settings.FindLogFor(this.name);
                }

                this.destinationLog = value;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="LogLevel"/> this Logger uses
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If value is not a valid <see cref="LogLevel"/>
        /// </exception>
        public LogLevel Level
        {
            get { return this.level; }
            set { this.level = value.Valid("value"); }
        }

        /// <summary>
        /// Gets or sets the <see cref="MessageFormatter"/> this Logger uses
        /// </summary>
        /// <remarks>
        /// When setting the <see cref="MessageFormatter"/> if you use <code>null</code>,
        /// the <see cref="MessageFormatter"/> will revert to what is contained in
        /// the <see cref="Settings"/> this Logger was created from.
        /// </remarks>
        public MessageFormatter MessageFormatter
        {
            get
            {
                return this.messageFormatter;
            }

            set
            {
                if (value == null)
                {
                    value = this.settings.FindMessageFormatterFor(this.name);
                }

                this.messageFormatter = value;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="ExceptionFormatter"/> this Logger uses
        /// </summary>
        /// <remarks>
        /// When setting the <see cref="ExceptionFormatter"/> if you use <code>null</code>,
        /// the <see cref="ExceptionFormatter"/> will revert to what is contained in
        /// the <see cref="Settings"/> this Logger was created from.
        /// </remarks>
        public ExceptionFormatter ExceptionFormatter
        {
            get
            {
                return this.exceptionFormatter;
            }

            set
            {
                if (value == null)
                {
                    value = this.settings.FindExceptionFormatterFor(this.name);
                }

                this.exceptionFormatter = value;
            }
        }

        /// <summary>
        /// Indicates whether the Logger will log at a particular level
        /// </summary>
        /// <param name="queryLevel">The <see cref="LogLevel"/> to check</param>
        /// <returns>
        /// True if the current logger will log at <paramref name="level"/>, 
        /// false otherwise.
        /// </returns>
        public bool WillLogAt(LogLevel queryLevel)
        {
            return
                //// would the log even accept the message?
                this.destinationLog.IsLogging
                //// is the level valid?
                && queryLevel.IsValid()
                //// does our level allow it?
                && this.level >= queryLevel;
        }

        /// <summary>
        /// Logs the <paramref name="message"/> at the specified <paramref name="level"/>
        /// </summary>
        /// <param name="message">The message to log</param>
        /// <param name="messageLevel">The level to log the message</param>
        public void Log(string message, LogLevel messageLevel)
        {
            if (this.WillLogAt(messageLevel))
            {
                this.destinationLog.WriteLine(this.Format(message, messageLevel), messageLevel);
            }
        }

        /// <summary>
        /// Logs the <paramref name="message">generated message</paramref> at the
        /// specified <paramref name="level"/>
        /// </summary>
        /// <param name="message">The <see cref="MessageGenerator">message</see> to log</param>
        /// <param name="messageLevel">The level to log the message</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <paramref name="level"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Log(MessageGenerator message, LogLevel messageLevel)
        {
            if (this.WillLogAt(messageLevel))
            {
                string userMessage = string.Empty;
                if (message != null)
                {
                    userMessage = message();
                }

                this.destinationLog.WriteLine(this.Format(userMessage, messageLevel), messageLevel);
            }
        }

        /// <summary>
        /// Logs the exception at the specified <paramref name="exceptionLevel"/>
        /// </summary>
        /// <param name="ex">The exception to log</param>
        /// <param name="exceptionLevel">The level to log the exception</param>
        public void Log(Exception ex, LogLevel exceptionLevel)
        {
            if (this.WillLogAt(exceptionLevel))
            {
                this.destinationLog.WriteLine(this.Format(ex, exceptionLevel), exceptionLevel);
            }
        }

        /// <summary>
        /// Logs the message at the <see cref="LogLevel.Verbose"/> level.
        /// </summary>
        /// <param name="message">The message to log</param>
        public void Verbose(string message)
        {
            this.Log(message, LogLevel.Verbose);
        }

        /// <summary>
        /// Logs the generated message at the <see cref="LogLevel.Verbose"/> level.
        /// </summary>
        /// <param name="message">The delegate that generates the message to log</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <see cref="LogLevel.Verbose"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Verbose(MessageGenerator message)
        {
            this.Log(message, LogLevel.Verbose);
        }

        /// <summary>
        /// Logs the exception at the <see cref="LogLevel.Verbose"/> level.
        /// </summary>
        /// <param name="ex">The exception to log</param>
        public void Verbose(Exception ex)
        {
            this.Log(ex, LogLevel.Verbose);
        }

        /// <summary>
        /// Logs the message at the <see cref="LogLevel.Debug"/> level.
        /// </summary>
        /// <param name="message">The message to log</param>
        public void Debug(string message)
        {
            this.Log(message, LogLevel.Debug);
        }

        /// <summary>
        /// Logs the generated message at the <see cref="LogLevel.Debug"/> level.
        /// </summary>
        /// <param name="message">The delegate that generates the message to log</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <see cref="LogLevel.Debug"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Debug(MessageGenerator message)
        {
            this.Log(message, LogLevel.Debug);
        }

        /// <summary>
        /// Logs the exception at the <see cref="LogLevel.Debug"/> level.
        /// </summary>
        /// <param name="ex">The exception to log</param>
        public void Debug(Exception ex)
        {
            this.Log(ex, LogLevel.Debug);
        }

        /// <summary>
        /// Logs the message at the <see cref="LogLevel.Inform"/> level.
        /// </summary>
        /// <param name="message">The message to log</param>
        public void Inform(string message)
        {
            this.Log(message, LogLevel.Inform);
        }

        /// <summary>
        /// Logs the generated message at the <see cref="LogLevel.Inform"/> level.
        /// </summary>
        /// <param name="message">The delegate that generates the message to log</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <see cref="LogLevel.Inform"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Inform(MessageGenerator message)
        {
            this.Log(message, LogLevel.Inform);
        }

        /// <summary>
        /// Logs the exception at the <see cref="LogLevel.Inform"/> level.
        /// </summary>
        /// <param name="ex">The exception to log</param>
        public void Inform(Exception ex)
        {
            this.Log(ex, LogLevel.Inform);
        }

        /// <summary>
        /// Logs the message at the <see cref="LogLevel.Warn"/> level.
        /// </summary>
        /// <param name="message">The message to log</param>
        public void Warn(string message)
        {
            this.Log(message, LogLevel.Warn);
        }

        /// <summary>
        /// Logs the generated message at the <see cref="LogLevel.Warn"/> level.
        /// </summary>
        /// <param name="message">The delegate that generates the message to log</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <see cref="LogLevel.Warn"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Warn(MessageGenerator message)
        {
            this.Log(message, LogLevel.Warn);
        }

        /// <summary>
        /// Logs the exception at the <see cref="LogLevel.Warn"/> level.
        /// </summary>
        /// <param name="ex">The exception to log</param>
        public void Warn(Exception ex)
        {
            this.Log(ex, LogLevel.Warn);
        }

        /// <summary>
        /// Logs the message at the <see cref="LogLevel.Error"/> level.
        /// </summary>
        /// <param name="message">The message to log</param>
        public void Error(string message)
        {
            this.Log(message, LogLevel.Error);
        }

        /// <summary>
        /// Logs the generated message at the <see cref="LogLevel.Error"/> level.
        /// </summary>
        /// <param name="message">The delegate that generates the message to log</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <see cref="LogLevel.Error"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Error(MessageGenerator message)
        {
            this.Log(message, LogLevel.Error);
        }

        /// <summary>
        /// Logs the exception at the <see cref="LogLevel.Error"/> level.
        /// </summary>
        /// <param name="ex">The exception to log</param>
        public void Error(Exception ex)
        {
            this.Log(ex, LogLevel.Error);
        }

        /// <summary>
        /// Logs the message at the <see cref="LogLevel.Fatal"/> level.
        /// </summary>
        /// <param name="message">The message to log</param>
        public void Fatal(string message)
        {
            this.Log(message, LogLevel.Fatal);
        }

        /// <summary>
        /// Logs the generated message at the <see cref="LogLevel.Fatal"/> level.
        /// </summary>
        /// <param name="message">The delegate that generates the message to log</param>
        /// <remarks>
        /// <para>
        /// The delegate will only be called if this Logger's <see cref="LogLevel"/> is
        /// greater than or equal to <see cref="LogLevel.Fatal"/>
        /// </para>
        /// <para>
        /// If the delegate is null, <see cref="System.String.Empty"/> is passed in its place.
        /// </para>
        /// </remarks>
        public void Fatal(MessageGenerator message)
        {
            this.Log(message, LogLevel.Fatal);
        }

        /// <summary>
        /// Logs the exception at the <see cref="LogLevel.Fatal"/> level.
        /// </summary>
        /// <param name="ex">The exception to log</param>
        public void Fatal(Exception ex)
        {
            this.Log(ex, LogLevel.Fatal);
        }

        private void CommonInit()
        {
            this.level = this.settings.FindLogLevelFor(this.name);
            this.messageFormatter = this.settings.FindMessageFormatterFor(this.name);
            this.exceptionFormatter = this.settings.FindExceptionFormatterFor(this.name);
        }

        private string Format(string message, LogLevel messageLevel)
        {
            return this.messageFormatter(message, this.name, messageLevel);
        }

        private string Format(Exception ex, LogLevel exceptionLevel)
        {
            return this.exceptionFormatter(ex, this.name, exceptionLevel);
        }
    }
}
